<?php
/**
 * @author thanh.dang
 * @version 1.0
 */

class Libs_Category
{
    private static $instance;
    private static $cache;
    
    private function __construct() {
        
    }
    
    public static function getInstance()
    {
        if(!isset(self::$instance)){
            return new Libs_Category();
        }
        return self::$instance;
    }
    
    /**
     *
     * @param type $id 
     * @return Category
     */
    public function getCategoryById($id)
    {
        if(!is_numeric($id))
            return false;
        return CategoryTable::getInstance()->findOneById($id);
    }
    
    /**
     * 
     * Enter description here ...
     * @param unknown_type $name
     */
    public function getCategoryBySlug($slug)
    {
    	if(is_null($slug))
    		return false;
    	return CategoryTable::getInstance()->findOneBySlug($slug);
    }
    
    /**
     * If we don't have any category yet, we need to create the first one and return it
     */
    public function getRoot($default = 'root')
    {
        $root = CategoryTable::getInstance()->findOneByName($default);
        if(!$root){
            $root = new Category();
            $root->name = $default;
            $root->save();
            CategoryTable::getInstance()->getTree()->createRoot($root);
        }        
        return $root;
    }
    
    /**
     *
     * @return type array
     */
    public function getRoots($default = 'root')
    {
        return CategoryTable::getInstance()->getTree()->fetchRoots();
    }
    
    /**
     *
     * @param type $formData include category information (name,parent)
     */
    public function saveCategory($formData,$isNew = true)
    {
        if($isNew){
            return $this->addNewCategory($formData);
        }else{
            return $this->updateCategory($formData);    
        }
    }
    
    /**
     *
     * @param type $formData
     * @param type $parent_id
     * @return mixed : return false if not success, 
     */
    private function addNewCategory($formData)
    {
        if(isset($formData['parent_id']) && is_numeric($formData['parent_id'])){
            $parent_id = $formData['parent_id'];
            unset($formData['parent_id']);
        }else{
            $parent_id = null;
        }
        
        $category = new Category();
        
        try{
            $category->active = Arr::get($formData,'id',0);
            $category->setArray($formData);
            $category->save();

            if(is_null($parent_id)){
                //Add as last child of root node
                $root = $this->getRoot();
                $this->addAsLastChild($category, $root);
                
            }else{
                $parent = $this->getCategoryById($parent_id);
                if(!$parent)
                    return new Error(array(
                        'error' => "An error occured."
                    ));                
                $this->addAsLastChild($category,$parent);
            }

            return $category;

        }catch(Doctrine_Validator_Exception $ex){
            return new Error($category->getErrorStack());
        }
    }
    
    /**
     *
     * @param type $formData
     * @return Error 
     */
    private function updateCategory($formData)
    {
        if(is_null(Arr::get($formData, 'id'))){
            return new Error(array(
                'error' => "Category not found."
            )); 
        }
        try{
            $category = $this->getCategoryById(Arr::get($formData,'id'));

            $category->active = Arr::get($formData,'active',0);
            //collect fields we need to update to database
            $formData = Arr::extract($formData,array('name','description'));
            $category->setArray($formData);
            $category->save();
            return $category;
        }catch(Doctrine_Validator_Exception $ex){
            return new Error($category->getErrorStack());
        }
    }
    
    /**
     * 
     * @param type $category : which category will be moved
     * @param type $target : target category move to
     * @param type $position : which type of position $category will move to $target (lastChild,firstChild,sibling....)
     */
    public function moveCategory($category, $target,$position)
    {
        
    }

    private function addAsLastChild($category,$target)
    {
        $category->getNode()->insertAsLastChildOf($target);
    }

    private function addAsFirstChild($category,$target)
    {
        $category->getNode()->insertAsFirstChildOf($target);
    }

    /**
     * Render all category in selectbox html 
     * @param unknown_type $root_name : which root node do u want to render (default 'root' name will be selected)
     * @param unknown_type $view : view file for render selectbox (default view : 'category/render/selectbox' )
     * @param unknown_type $name : name of selectbox
     * @param unknown_type $selected : id of category which selectbox will selected (default false)
     * @param unknown_type $explode : id of category which selectbox will not contain (defaut false)
     */
    public function render($name = '',$root_name = 'root',$selected = false,$explode = false,$view = 'category/render/selectbox')
    {
    	return View::admin($view)->set(array(
    				'nodes' => $this->getAllCategoryByRoot($root_name),
    				'explode' => $explode,
    				'name' => $name,
    				'selected' => $selected
    			));
    }
    
    public function getAllCategoryByRoot($root_name = 'root')
    {
    	if(!isset(self::$cache[$root_name])){
    		$root = $this->getRoot($root_name);
            $descendants = $root->getNode()->getDescendants(null,true);
    		self::$cache[$root->name] = (!$descendants) ? array() : $descendants->toArray();
    	}
    	
    	return self::$cache[$root_name];
    }
    
	/**
     * Get Tree Category
     * @example
     *          Array(
     *                  0 => Array(
     *                          'id' => 1
     *                          'children' => Array()
     *                      ),
     *                  1 => Array(
     *                          'id' => 2
     *                      )
     *              )
     * @param <type> $include_root
     * @return <type> Array
     */
    public function getTree($root_name = 'root',$include_root = true)
    {
        $tree = $this->getAllCategoryByRoot($root_name);

        if(!$include_root)
            array_shift ($tree);
        
        return $this->parseTree($tree);
    }
    
	/**
     * Return List Child Category Has Structured
     * @param <type> $list list category has structered
     * @param <type> $id
     * @return <type> Array
     */
    private function getChildren($list,$id)
    {
        $result = array();
        
        foreach($list as $row){
            
            if($row['id'] == $id){
                return $row['children'];
            }

            if(isset($row['children'])){
                //call recursion
                $result = array_merge($result,$this->getChildren($row['children'],$id));
            }
        }
        return $result;
    }
    
    /**
     * parse List Tree
     * @param <type> $list
     * @return <type> Array
     */
    public function parseTree(&$list)
    {
        $result = array();
        
        if(count($list) == 1)
            return $list;
        
        while(count($list) > 1)
        {
            $node = array_shift($list);
            
            if($list[0]['level'] > $node['level']){
                $node['children'] = $this->parseTree($list);
            }
            elseif($list[0]['level'] < $node['level']){
                return array_merge ($result,array($node));
            }

            $result[] = $node;

            if(count($list) == 1)
            {
                if($list[0]['level'] == $node['level'])
                    $result[] = $list[0];
                elseif($list[0]['level'] > $node['level'])
                    $node['children'] = $list;
                else
                    return $list;
            }
        }

        return $result;
    }
}